package com.linsen.erp.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.linsen.erp.admin.entity.MenuEntity;
import com.linsen.erp.admin.mapper.MenuMapper;
import com.linsen.erp.admin.provider.dto.MenuDTO;
import com.linsen.erp.admin.provider.enums.MenuTypeEnum;
import com.linsen.erp.admin.provider.vo.MenuVO;
import com.linsen.erp.admin.service.IMenuService;
import com.linsen.core.common.context.TokenContextHolder;
import com.linsen.core.common.enums.DeleteType;
import com.linsen.core.common.enums.YnType;
import com.linsen.core.common.exception.BaseException;
import com.linsen.core.common.model.User;
import com.linsen.core.common.tree.TreeUtils;
import com.linsen.core.common.util.ConvertUtil;
import com.linsen.core.redis.util.RedisSecurityUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单 服务实现类
 * </p>
 *
 * @author zhaoyong
 * @since 2023-07-24
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, MenuEntity> implements IMenuService {

    private final RedisSecurityUtil redisSecurityUtil;

    /** 根据userid获取关联菜单列表 */
    private List<MenuEntity> getMenuByUserid(String userid, Integer isAdmin){
        List<MenuEntity> menuEntityList;
        if (ObjectUtil.equal(isAdmin, YnType.YES.getValue())){
            //默认获取全部菜单
            menuEntityList = getAllMenu();
        }else{
            menuEntityList = this.getBaseMapper().getByUserid(userid);
        }

        return menuEntityList;
    }

    private List<MenuEntity> getAllMenu(){
        return this.list(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
                        .orderByAsc(MenuEntity::getPriority)
        );
    }

    @Override
    public Set<String> getPermissionByUserid(String userid, Integer isAdmin) {
        return getMenuByUserid(userid,isAdmin).stream().map(MenuEntity::getPermission)
                .filter(ObjectUtil::isNotEmpty)
                .map(permission -> permission.trim().split(";"))
                .flatMap(Arrays::stream)
                .filter(ObjectUtil::isNotEmpty)
                .collect(Collectors.toSet());
    }

    @Override
    public Set<String> getUserPermissions() {
        User user = this.redisSecurityUtil.getUser(TokenContextHolder.getToken());
        String userid = user.getUserid();
        Integer isAdmin = user.getIsAdmin();

        return getPermissionByUserid(userid,isAdmin);
    }

    @Override
    public List<MenuVO> getUserMenuNavList() {
        User user = this.redisSecurityUtil.getUser(TokenContextHolder.getToken());
        String userid = user.getUserid();
        Integer isAdmin = user.getIsAdmin();

        List<MenuEntity> menuEntityList = getMenuByUserid(userid, isAdmin);

        menuEntityList.removeIf(menuEntity ->
                ObjectUtil.equal(menuEntity.getType(), MenuTypeEnum.BUTTON.getValue())
        );

        return TreeUtils.buildTree(ConvertUtil.convert(menuEntityList,MenuVO.class));
    }

    @Override
    public List<MenuVO> getMenuList() {
        return TreeUtils.buildTree(ConvertUtil.convert(getAllMenu(),MenuVO.class));
    }

    /** 新增/修改菜单时，判断菜单类型是否正常 */
    private void checkMenuType(Integer type,String pid){
        if (ObjectUtil.equal(type, MenuTypeEnum.MODULE.getValue())) {
            if (ObjectUtil.isNotNull(pid)) {
                throw new BaseException("模块必须作为顶级节点存在");
            }
        }else if (ObjectUtil.equal(type, MenuTypeEnum.CATALOGUE.getValue())) {
            if (ObjectUtil.isNotNull(pid)) {
                MenuEntity pMenuEntity = this.getById(pid);
                Integer pType = pMenuEntity.getType();
                if (!ObjectUtil.equal(pType, MenuTypeEnum.MODULE.getValue()) &&
                        !ObjectUtil.equal(pType, MenuTypeEnum.CATALOGUE.getValue())) {
                    throw new BaseException("目录的上级仅限模块或目录");
                }
            }else{
                throw new BaseException("目录不允许作为顶级节点存在");
            }
        } else if (ObjectUtil.equal(type, MenuTypeEnum.MENU.getValue())) {
            if (ObjectUtil.isNotNull(pid)) {
                MenuEntity pMenuEntity = this.getById(pid);
                Integer pType = pMenuEntity.getType();
                if (!ObjectUtil.equal(pType, MenuTypeEnum.MODULE.getValue()) &&
                        !ObjectUtil.equal(pType, MenuTypeEnum.CATALOGUE.getValue())) {
                    throw new BaseException("菜单的上级仅限模块或目录");
                }
            }
        } else if (ObjectUtil.equal(type, MenuTypeEnum.BUTTON.getValue())) {
            if (ObjectUtil.isNotNull(pid)) {
                MenuEntity pMenuEntity = this.getById(pid);
                Integer pType = pMenuEntity.getType();
                if (!(ObjectUtil.equal(pType, MenuTypeEnum.MENU.getValue()) ||
                        ObjectUtil.equal(pType, MenuTypeEnum.BUTTON.getValue()))) {
                    //通过按钮跳转内页时，内页页面上的按钮依然也有权限，这时候为了方便管理和查阅，依然按树形层级结构设置菜单信息（内页按钮权限在外页按钮权限之下）
                    throw new BaseException("按钮的上级仅限菜单或按钮");
                }
            }else{
                throw new BaseException("按钮不允许作为顶级节点存在");
            }
        }
    }

    @Transactional
    @Override
    public void saveMenu(MenuDTO menuDTO) {
        Integer type = menuDTO.getType();
        String pid = menuDTO.getPid();
        String name = menuDTO.getName();

        checkMenuType(type,pid);

        List<MenuEntity> menuEntityList = this.list(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getName, name)
                        .eq(MenuEntity::getPid, pid)
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
        );
        if (ObjectUtil.isNotEmpty(menuEntityList)){
            throw new BaseException("同级菜单不能重名");
        }

        this.save(ConvertUtil.convert(menuDTO,MenuEntity.class));
    }

    @Transactional
    @Override
    public void updateMenu(MenuDTO menuDTO) {
        String id = menuDTO.getId();
        Integer type = menuDTO.getType();
        String pid = menuDTO.getPid();
        String name = menuDTO.getName();

        //存在下级节点时，不允许修改菜单类型（类型变更后，会导致新的上级菜单和原有的下级菜单异常）
        List<String> subMenuIdList = CollectionUtil.newArrayList();
        getSubMenuIdList(subMenuIdList, id);
        if (ObjectUtil.isNotEmpty(subMenuIdList)){
            MenuEntity oldMenuEntity = this.getById(id);
            if (!ObjectUtil.equal(oldMenuEntity.getType(),type)){
                throw new BaseException("存在下级节点时，不允许修改菜单类型");
            }
        }

        if (ObjectUtil.isNotNull(pid)) {
            if (ObjectUtil.equal(pid, id)) {
                throw new BaseException("不能选择自己作为新的父级节点");
            }

            if (subMenuIdList.contains(pid)) {
                throw new BaseException("不能选择下级节点作为新的父级节点");
            }
        }

        checkMenuType(type,pid);

        List<MenuEntity> menuEntityList = this.list(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getName, name)
                        .eq(MenuEntity::getPid, pid)
                        .ne(MenuEntity::getId, id)
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
        );
        if (ObjectUtil.isNotEmpty(menuEntityList)){
            throw new BaseException("同级菜单不能重名");
        }

        this.updateById(ConvertUtil.convert(menuDTO,MenuEntity.class));
    }

    /** 获取指定菜单包含的全部子级菜单id（循环嵌套查询所有子级） */
    private void getSubMenuIdList(List<String> subMenuIdList, String pid) {
        this.list(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getPid, pid)
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
        ).forEach(menuEntity -> {
            String id = menuEntity.getId();
            subMenuIdList.add(id);
            getSubMenuIdList(subMenuIdList, id);
        });
    }

    @Transactional
    @Override
    public void delete(String id) {
        //有子级菜单时，不允许直接删除（敏感操作）
        long count = this.count(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getPid, id)
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
        );
        if (count > 0) {
           throw new BaseException("存在子级节点，不允许直接删除！");
        }

        this.update(
                new UpdateWrapper<MenuEntity>().lambda()
                        .set(MenuEntity::getIsDeleted,DeleteType.DELETED.getValue())
                        .eq(MenuEntity::getId,id)
        );
    }

    @Transactional
    @Override
    public void fetchDelete(String id) {
        List<String> subMenuIdList = CollectionUtil.newArrayList();
        subMenuIdList.add(id);
        getSubMenuIdList(subMenuIdList, id);

        this.update(
                new UpdateWrapper<MenuEntity>().lambda()
                        .set(MenuEntity::getIsDeleted,DeleteType.DELETED.getValue())
                        .in(MenuEntity::getId,subMenuIdList)
        );
    }

    @Transactional
    @Override
    public void copy(String id) {
        copy(this.getById(id),true,null);
    }

    @Transactional
    @Override
    public void fetchCopy(String id) {
        MenuEntity firstMenuEntity = copy(this.getById(id), true, null);
        subCopy(id,firstMenuEntity.getId());
    }

    private MenuEntity copy(MenuEntity menuEntity,boolean isFirst,String pid){
        menuEntity.setId(null);
        if (!isFirst){
            menuEntity.setPid(pid);
        }
        menuEntity.setName(menuEntity.getName() + (isFirst?"COPY":""));
        menuEntity.setCreateBy(null);
        menuEntity.setCreateTime(null);
        menuEntity.setUpdateBy(null);
        menuEntity.setUpdateTime(null);

        this.save(menuEntity);

        return menuEntity;
    }
    private void subCopy(String pid,String newPid){
        this.list(
                new LambdaQueryWrapper<MenuEntity>()
                        .eq(MenuEntity::getPid, pid)
                        .eq(MenuEntity::getIsDeleted, DeleteType.NORMAL.getValue())
        ).forEach(menuEntity -> {
            MenuEntity subMenuEntity = copy(menuEntity, false, newPid);
            subCopy(menuEntity.getId(),subMenuEntity.getId());
        });
    }
}
